using Gee;

namespace Vwf.Tools
{
	
	public errordomain EDStrParser {
		CursorNotExists,
		CursorOutOfRange,
	}
	/// <summary>
	/// Hledá a vytahuje části řetězce.
	/// </summary>
	public class StrParser
	{
		bool logEnabled = false;
		// string str;
		UString str = new UString();
		HashMap<string, int> cursors = new HashMap<string, int>();
		int cursor = 0;
		int cursorEnd = 0;

		protected void logMsg(string msg) {
			if (logEnabled) stdout.printf(msg + " #" + cursor.to_string() + " [" + str.substring(cursor, 20) + "]\n");
		}
		
		/// <summary>
		/// Inicializuje třídu
		/// </summary>
		/// <param name="iStr">Vstupní řetězec</param>
		public StrParser(string iStr) {
			setStr(iStr);
		}
		/// <summary>
		/// Nastavy novy vstupni retezec
		/// </summary>
		/// <param name="iStr">Vstupní řetězec</param>
		public void setStr(string iStr) {
			str.str = iStr;
			cursor = str.length - 1;
			storeCursor("_end");
			cursor = 0;
			storeCursor("_begin");
		}

		public string? lastChunk = null;
		
		public int? lastChunkIndex = null;
		
		/// <summary>
		/// Najde prvni vyskyty řetězců od kurzoru a nastaví kurzor na první výskyt.
		/// </summary>
		/// <param name="chunks">Hledané řetězce</param>
		/// <returns>true pokud byl řetezec nalezen</returns>
		public bool findAnyNext(string[] chunks)
		{
			logMsg("findAnyNext(" + string.joinv(", ", chunks) + ") ");
			int firstPos = -1;
			int lenght = 0;
			lastChunk = null;
			lastChunkIndex = null;
			
			int chunkIndex = 0;
			foreach (string chunk in chunks)
			{
				int pos = str.indexOf(chunk, cursor);
				if (pos != -1)
				{
					if (firstPos == -1 || (pos < firstPos))
					{
						firstPos = pos;
						lenght = chunk.length;
						lastChunk = chunk;
						lastChunkIndex = chunkIndex;
				   };
				};
				chunkIndex++;
			};
			if (firstPos != -1)
			{
				cursor = firstPos;
				cursorEnd = cursor + lenght;
				
				logMsg("findAnyNext return (" + lastChunk + ") ");
				return true;
			}
			else
			{
				return false;
			};
		}

		/// <summary>
		/// Najde prvni vyskyt retezce od kurzoru a nastaví kurzor.
		/// </summary>
		/// <param name="chunk">Hledaný řetězec</param>
		/// <returns>true pokud byl řetezec nalezen</returns>
		public bool findNext(string chunk)
		{
			return findAnyNext(new string[] { chunk });
		}

		/// <summary>
		/// Uloží záložku kurzoru
		/// </summary>
		/// <param name="name">Jméno záložky</param>
		/// <param name="useEnd">true - uloží konec nalezeného řetězce, false - začátek</param>
		public string storeCursor(string? name = null, bool useEnd = false)
		{
			string cursorName = (name != null) ? name : createCursor();
			
			cursors[cursorName] = useEnd ? cursorEnd : cursor;
			return cursorName;
			/* if (cursors.ContainsKey(name))
			{
				cursors[name] = useEnd ? cursorEnd : cursor;
			}
			else
			{
				cursors.Add(name, useEnd ? cursorEnd : cursor);
			}; */
			
		}

		/// <summary>
		/// Vrátí pozici záložky
		/// </summary>
		/// <param name="name">Jméno záložky</param>
		/// <returns></returns>
		public int getCursor(string name) throws EDStrParser
		{
			if (name in cursors)
			{
				return cursors[name];
			}
			else
			{
				// throw new Exception("Cursor with name '" + name + "' not exists");
				throw new EDStrParser.CursorNotExists("Cursor with name '" + name + "' not exists");
			};
		}

		/// <summary>
		/// Nastaví kurzor ze záložky
		/// </summary>
		/// <param name="name">Jméno záložky</param>
		public void setCursor(string name) throws EDStrParser
		{
			cursor = getCursor(name);
		}

		/// <summary>
		/// Vrátí text mezi dvěma záložkama. Pokud je druhá záložka null, pouzije aktuální pozici kurzoru.
		/// </summary>
		/// <param name="from">začáteční záložka</param>
		/// <param name="to">koncová záložka</param>
		/// <returns></returns>
		public string substring(string from, string? to = null) throws EDStrParser
		{
			int fromPos = getCursor(from);
			int toPos = to != null ? getCursor(to) : cursor;

			if (fromPos > toPos)
			{
				int tmp = fromPos;
				fromPos = toPos;
				toPos = tmp;
			};
			return str.substring(fromPos, toPos - fromPos);
			/* int pad = 7;
			return str.substring(fromPos - pad, pad) + "[" + fromPos + "]" + str.substring(fromPos, pad)
				+ str.substring(toPos - pad, pad) + "[" + toPos + "]" + str.substring(toPos, pad);
			return "[" + fromPos + ">" + str.substring(fromPos, pad) + str.substring(toPos - pad, pad) + "<" + toPos + "]"; */
		}

		/// <summary>
		/// Posune kurzor dopredu nebo do zadu
		/// </summary>
		/// <param name="shift">Pocet znaku o ktere se ma kurzor posunout + dopredu, - dozadu.</param>
		public void rmove(int shift) throws EDStrParser
		{
			if (cursor + shift <= getCursor("_end"))
			{
				cursor += shift;
			}
			else
			{
				throw new EDStrParser.CursorOutOfRange("rmove: Cursor out of range. " + (cursor + shift).to_string() + " > " + getCursor("_end").to_string() + ".");
			};
		}

		/// <summary>
		/// Posune kurzor na konec vyhledaného retezce
		/// </summary>
		public void toEndChunk()
		{
			cursor = cursorEnd;
		}

		/// <summary>
		/// Smaze ulozene kurzory a kurzor nastavi na zacatek textu.
		/// </summary>
		public void reset()
		{
			cursors.clear();
			setStr(str.str);
		}
		
		protected int cursorCounter = 0;
		public string createCursor() {
			string cursorName = "__";
			do {
				cursorCounter++;
				cursorName = "__" + cursorCounter.to_string();
			} while (cursorName in cursors);
			
			return cursorName;
		}
		
		public int findNextNonChar(string chars) {
			UString uchars = new UString(chars);
			int startPos = cursor;
			while(true) {
				bool ok = false;
				for (int i = 0; i < uchars.length; i++) {
					if (uchars.charAt(i) == charAtCursor()) {
						ok = true;
						break;
					}
				}
				if (!ok) return cursor - startPos;
				rmove(1);
			}
		}
		
		public string charAtCursor() {
			return str.charAt(cursor);
		}
		
		public bool followedBy(string chunk) {
			var uChunk = new UString(chunk);
			if (cursor + uChunk.length > str.length) return false;
			return str.substring(cursor, uChunk.length)  == chunk;
		}
		
		public bool eos() {
			return cursor >= str.length;
		}
		
		public int lineNum {
			get {
				int n = 1;
				for(int i = 0; i < cursor; i++) {
					if (str.charAt(i) == "\n") n++;
				}
				return n;
			}
		}
		
		public int colNum {
			get {
				int n = 1;
				for(int i = cursor; i >= 0; i--) {
					if (str.charAt(n) == "\n") break;
					n++;
				}
				return n;
			}
		}
		
	}
}
